#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This program is free software; you can redistribute it and/or modify  
# it under the terms of the GNU General Public License as published by  
# the Free Software Foundation; either version 2 of the License, or     
# (at your option) any later version.                                   
#                                                                         
# A copy of the license can be found in the license.txt file supplied   
# with this software or at: http://www.gnu.org/copyleft/gpl.html       
#


#
# Commit Log tracks every write operation into the system. The aim
# of the commit log is to be able to successfully recover data that was
# not stored to disk via the Memtable. Every Commit Log maintains a
# header represented by the abstraction CommitLogHeader. The header
# contains a bit array and an array of longs and both the arrays are
# of size, #column families for the Table, the Commit Log represents.
#
# Whenever a ColumnFamily is written to, for the first time its bit flag
# is set to one in the CommitLogHeader. When it is flushed to disk by the
# Memtable its corresponding bit in the header is set to zero. This helps
# track which CommitLogs can be thrown away as a result of Memtable flushes.
# Additionally, when a ColumnFamily is flushed and written to disk, its
# entry in the array of longs is updated with the offset in the Commit Log
# file where it was written. This helps speed up recovery since we can seek
# to these offsets and start processing the commit log.
#
# Every Commit Log is rolled over everytime it reaches its threshold in size;
# the new log inherits the "dirty" bits from the old.
#
# Over time there could be a number of commit logs that would be generated.
# To allow cleaning up non-active commit logs, 
# whenever we flush a column family and update its bit flag in the active CL, 
# we take the dirty bit array and bitwise & it with the headers of the older logs.
# If the result is 0, then it is safe to remove the older file.  
# (Since the new CL inherited the old's dirty bitflags, 
# getting a zero for any given bit in the anding means that 
# either the CF was clean in the old CL or it has been flushed since the switch in the new.)
#

import os
import time


from configure import Configure

from row import Row

class   CommitLogContext(object) :
    def __init__(self, filename, position = -1) :
        self.filename = filename
        self.position = position
        ## __init__()

    def isValid(self) :
        return self.position != -1
        ## isValid()
    ## CommitLogContext()


class   CommitLog(object) :
    commit_log_instance = None

    @classmethod
    def instance(cls) :
        if cls.commit_log_instance :
            return cls.commit_log_instance

        #print 'Singletone : create CommitLog instance ...'
        cls.commit_log_instance = CommitLog()

        return cls.commit_log_instance
        ## instance()


    def __init__(self) :
        # list of commit-log files
        # map: table_name -> file_handle
        self._files = {}

        # TODO : if there are files in commit-log directory, we should recover table firstly.

        ## __init__()

    def __del__(self) :
        for table_name,fp in self._files.iteritems() :
            fp.close()
        ## __del__()

    def __getNextFileName(self, table_name) :
        '''
        generates a filename of format "commitlog-<table>-<timestamp>.log" in commit-log directory
        specified by database configuration
        '''
        return '%s%scommitlog-%s-%s.log' % (Configure.instance().dir_commit_log, os.sep, table_name, time.strftime('%Y%m%d%H%M%S'))
        ## __getNextFileName()

    def __getWriter(self, table_name) :
        if table_name in self._files :
            return self._files[ table_name ]

        filename = self.__getNextFileName(table_name)
        fp = file(filename,'a')
        self._files[ table_name ] = fp
        return fp
        ## __getWriter()

    def add(self, row) :
        if not row or not row.table_name :
            return None
        writer = self.__getWriter(row.table_name)
        clog_ctx = CommitLogContext(writer.name, writer.tell())

        # TODO : to update CommitLogHeader

        row_serialized = Row.Serializer.serialize(row)
        row_serialized_length = len(row_serialized)
        writer.write("%10d\t%s\n" % (row_serialized_length, row_serialized))

        # TODO : to check and roll commitlog

        return clog_ctx
        ## add()

    ## CommitLog()


